/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package digitaldreamers.libraries.games.servers.gameserver;

import digitaldreamers.libraries.games.servers.networkmanagement.abstracts.AbPacketHandler;
import digitaldreamers.libraries.games.servers.networkmanagement.abstracts.IConnectionHandler;
import digitaldreamers.libraries.games.servers.networkmanagement.abstracts.IConnectionHandler.ConnectionStates;
import digitaldreamers.libraries.games.servers.networkmanagement.abstracts.IConnectionHandler.IOnConnectionErrorListener;
import digitaldreamers.libraries.games.servers.networkmanagement.abstracts.IPlayerComponent;
import digitaldreamers.libraries.games.servers.networkmanagement.concrete.ConnectionHandlerFactory;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;

/**
 *
 * @author DaoLe
 */
public abstract class AbCommonGameServer implements Runnable, ICommonGameServer, IOnConnectionErrorListener {

    // <editor-fold defaultstate="collapsed" desc="Constants">
    
    // <editor-fold defaultstate="collapsed" desc="Messages">
    private static final String MESSAGE__SERVER_STARTED = "Server is started.";
    private static final String MESSAGE__SERVER_STOPPED = "Server is stopped.";
    private static final String MESSAGE__CLIENT_CONNECTED = "A client has connected - IP: %s , Port: %s";
    private static final String MESSAGE__CONNECTION_ADDED = "Added a new connection.";
    private static final String MESSAGE__CONNECTION_REMOVED = "Removed a connection.";
    private static final String MESSAGE__THREAD_ENDED = "%s ended";
    private static final String CONNECTION_ACCEPTING_THREAD_NAME = "Connection accepting thread";
    // </editor-fold>
    
    private static final int SLEEP_TIME = 100;
    // </editor-fold>
    
    // <editor-fold defaultstate="collapsed" desc="Members">
    protected IGameServerUI mGameServerUI;
    protected int mServerPort;
    protected volatile boolean mRunning;
    protected Thread mConnectionAcceptingThread;
    protected ServerSocket mServerSocket;
    protected ArrayList<IConnectionHandler> mConnectionList;
    protected final Object mConnectionListLock;
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="Constructors">
    public AbCommonGameServer(IGameServerUI gameServerUI, int iPort) {
        mGameServerUI = gameServerUI;
        mServerPort = iPort;
        mConnectionList = new ArrayList<>();
        mConnectionListLock = new Object();
        mRunning = false;
    }
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="Methods">
    /**
     * Show message.
     *
     * @param pMessage The message to show.
     */
    @Override
    public void showMessage(String pMessage) {
        mGameServerUI.showMessage(pMessage);
    }
    
    @Override
    public void start() throws IOException {
        try {
            if (!mRunning) {
                mServerSocket = new ServerSocket(mServerPort);
                mRunning = true;
                mConnectionAcceptingThread = new Thread(this);
                mConnectionAcceptingThread.start();
                showMessage(MESSAGE__SERVER_STARTED);
            }
        } catch (IOException ex) {
            throw ex;
        }
    }
    
    @Override
    public void stop() throws InterruptedException, IOException {
        try {
            if (mRunning) {
                mServerSocket.close();
                mRunning = false;
                mConnectionAcceptingThread.join();
                while (mConnectionList.size() > 0) {
                    removeConnection(mConnectionList.get(mConnectionList.size() - 1));
                }
                showMessage(MESSAGE__SERVER_STOPPED);
            }
        } catch (InterruptedException ex) {
            throw ex;
        } catch (IOException ex) {
            throw ex;
        }
    }
    
    @Override
    public void run() {
        while (mRunning) {
            if (mServerSocket.isBound()) {
                try {
                    Socket socket = mServerSocket.accept();
                    showMessage(String.format(MESSAGE__CLIENT_CONNECTED, ((InetSocketAddress) socket.getRemoteSocketAddress()).getAddress(), ((InetSocketAddress) socket.getRemoteSocketAddress()).getPort()));
                    IConnectionHandler connectionHandler = ConnectionHandlerFactory.createNewConnectionHandler(socket, this, createNewPacketHandlerObject(), isConnectionCheckEnabled());
                    connectionHandler.setPlayer(createNewPlayerComponentObject(connectionHandler));
                    addNewConnection(connectionHandler);
                } catch (IOException ex) {
                } catch (Exception ex) {
                    showMessage(ex.getMessage());
                }
            }
            try {
                Thread.sleep(SLEEP_TIME);
            } catch (InterruptedException ex) {
                mGameServerUI.showMessage(ex.getMessage());
            }
        }
        System.out.println(String.format(MESSAGE__THREAD_ENDED, CONNECTION_ACCEPTING_THREAD_NAME));
    }
    
    protected void addNewConnection(IConnectionHandler pConnectionHandler) {
        synchronized (mConnectionListLock) {
            if (mConnectionList.add(pConnectionHandler)) {
                pConnectionHandler.addConnectionErrorListener(this);
                pConnectionHandler.start();
                showMessage(MESSAGE__CONNECTION_ADDED);
            }
        }
    }
    
    protected void removeConnection(IConnectionHandler pConnectionHandler) {
        synchronized (mConnectionListLock) {
            if (mConnectionList.remove(pConnectionHandler)) {
                pConnectionHandler.stop();
                pConnectionHandler.removeConnectionErrorListener(this);
                showMessage(MESSAGE__CONNECTION_REMOVED);
            }
        }
    }

    // <editor-fold defaultstate="collapsed" desc="Abstract methods">
    /**
     * Create new packet handler object.
     * 
     * @return New packet handler object.
     */
    protected abstract AbPacketHandler createNewPacketHandlerObject();
    
    /**
     * Enable connection status checking or not.
     * @return true: enable. Otherwise, false.
     */
    protected abstract boolean isConnectionCheckEnabled();
    
    /**
     * Create new player component object.
     * @param pConnectionHandler The connection handler for the created player object.
     * @return New player component object.
     */
    protected abstract IPlayerComponent createNewPlayerComponentObject(IConnectionHandler pConnectionHandler);
    
    /**
     * Handle the connection error event.
     *
     * @param pConnectionHandler The connection handler that raised the event.
     * @param pState The state of the connection.
     */
    @Override
    public abstract void onConnectionError(final IConnectionHandler pConnectionHandler, final ConnectionStates pState);
    // </editor-fold>
    
    // </editor-fold>
}
